package org.jeecg.common.util;

import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.jeecg.common.util.ConsoleColors.*;


/**
 * @Description
 * @Author Van.Planifolia
 * @Date 2024/1/11
 * @Version 1.0
 */
@Primary
@Component
@Slf4j
public class PlanifoliaRedisUtil extends RedisUtil {

    private static final Lock hgLock = new ReentrantLock();
    private static final Lock gLock = new ReentrantLock();

    @FunctionalInterface
    public interface CacheCallback<E> {
        E execute();
    }

    /**
     * 执行HGet查询，并且携带锁来防止击穿
     *
     * @param rKey     redisKey
     * @param eKey     entityKey
     * @param callback 回调方法，根据不同的需求进行不同的DB查询手段，使用方法见参阅
     * @return 从缓存中查询到的数据信息
     */
    public <E> E executeHGetWithLock(String rKey, String eKey, CacheCallback<E> callback) {
        long start = System.currentTimeMillis();
        // 取值
        Object resultFromCache = hget(rKey, eKey);
        log.info(Strings.toColor("redis尝试第一次取值....", YELLOW));
        // 非空判断，若空就上锁再查询
        if (Objects.isNull(resultFromCache)) {
            log.info(Strings.toColor("redis第一次取值为空！尝试上锁且第二次取值....", RED));
            hgLock.lock();
            log.info(Strings.toColor("Locked!", YELLOW));
            try {
                resultFromCache = hget(rKey, eKey);
                // 若查询到数据仍然为空，就走DB查询
                if (Objects.isNull(resultFromCache)) {
                    log.info(Strings.toColor("redis第二次取值为空！尝试从DB中取值....", RED));
                    E resultFromDB = callback.execute();
                    // 若DB不为空，就刷盘
                    if (Objects.nonNull(resultFromDB)) {
                        log.info(Strings.toColor("DB取值成功！开始往redis刷盘！", GREEN));
                        hset(rKey, eKey, resultFromDB, CommonConstant.COMM_BASE_ENTITY_ALIVE);
                    } else {
                        // 若为空，则防止穿透刷盘
                        log.info(Strings.toColor("DB取值为空！开始往redis刷入空值！", RED));
                        hset(rKey, eKey, CommonConstant.EMPTY_STR, 5);
                    }
                    // 直接返回DB查询值
                    log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
                    return resultFromDB;
                }
            } finally {
                hgLock.unlock();
                log.info(Strings.toColor("UnLocked", GREEN));
            }
        }
        log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
        return resultFromCache instanceof String ? null : (E) resultFromCache;
    }

    /**
     * 执行HGet查询，并且携带锁来防止击穿
     *
     * @param rKey      redisKey
     * @param eKey      entityKey
     * @param callback  回调方法，根据不同的需求进行不同的DB查询手段，使用方法见参阅
     * @param aliveTime 缓存存活事件
     * @return 从缓存中查询到的数据信息
     */
    public <E> E executeHGetWithLock(String rKey, String eKey, CacheCallback<E> callback, Long aliveTime) {
        long start = System.currentTimeMillis();
        // 取值
        Object resultFromCache = hget(rKey, eKey);
        log.info(Strings.toColor("redis尝试第一次取值....", YELLOW));
        // 非空判断，若空就上锁再查询
        if (Objects.isNull(resultFromCache)) {
            log.info(Strings.toColor("redis第一次取值为空！尝试上锁且第二次取值....", RED));
            hgLock.lock();
            log.info(Strings.toColor("Locked!", YELLOW));
            try {
                resultFromCache = hget(rKey, eKey);
                // 若查询到数据仍然为空，就走DB查询
                if (Objects.isNull(resultFromCache)) {
                    log.info(Strings.toColor("redis第二次取值为空！尝试从DB中取值....", RED));
                    E resultFromDB = callback.execute();
                    // 若DB不为空，就刷盘
                    if (Objects.nonNull(resultFromDB)) {
                        log.info(Strings.toColor("DB取值成功！开始往redis刷盘！", GREEN));
                        hset(rKey, eKey, resultFromDB, aliveTime);
                    } else {
                        // 若为空，则防止穿透刷盘
                        log.info(Strings.toColor("DB取值为空！开始往redis刷入空值！", RED));
                        hset(rKey, eKey, CommonConstant.EMPTY_STR, 5);
                    }
                    // 直接返回DB查询值
                    log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
                    return resultFromDB;
                }
            } finally {
                hgLock.unlock();
                log.info(Strings.toColor("UnLocked", GREEN));
            }
        }
        log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
        return resultFromCache instanceof String ? null : (E) resultFromCache;
    }

    /**
     * 执行Get查询，并且携带锁来防止击穿
     *
     * @param rKey     redisKey
     * @param callback 回调方法，根据不同的需求进行不同的DB查询手段，使用方法见参阅
     * @return 从缓存中查询到的数据信息
     */
    public <E> E executeGetWithLock(String rKey, CacheCallback<E> callback) {
        long start = System.currentTimeMillis();
        // 取值
        Object resultFromCache = get(rKey);
        log.info(Strings.toColor("redis尝试第一次取值....", YELLOW));
        // 非空判断，若空就上锁再查询
        if (Objects.isNull(resultFromCache)) {
            log.info(Strings.toColor("redis第一次取值为空！尝试上锁且第二次取值....", RED));
            gLock.lock();
            log.info(Strings.toColor("Locked!", YELLOW));
            try {
                resultFromCache = get(rKey);
                // 若查询到数据仍然为空，就走DB查询
                if (Objects.isNull(resultFromCache)) {
                    log.info(Strings.toColor("redis第二次取值为空！尝试从DB中取值....", RED));
                    E resultFromDB = callback.execute();
                    // 若DB不为空，就刷盘
                    if (Objects.nonNull(resultFromDB)) {
                        log.info(Strings.toColor("DB取值成功！开始往redis刷盘！", GREEN));
                        set(rKey, resultFromDB, CommonConstant.COMM_BASE_ENTITY_ALIVE);
                    } else {
                        // 若为空，则防止穿透刷盘
                        log.info(Strings.toColor("DB取值为空！开始往redis刷入空值！", RED));
                        set(rKey, CommonConstant.EMPTY_STR, 5);
                    }
                    // 直接返回DB查询值
                    log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
                    return resultFromDB;
                }
            } finally {
                gLock.unlock();
                log.info(Strings.toColor("UnLocked", GREEN));
            }
        }
        log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
        return resultFromCache instanceof String ? null : (E) resultFromCache;
    }


    /**
     * 缓存获取（Object自动转换Set类型）
     *
     * @param key   redis key
     * @param clazz 类型class
     * @return 转换后的list
     */
    public <T> Set<T> getSet(String key, Class<T> clazz) {
        Object o = this.get(key);
        if (Objects.nonNull(o)) {
            return CollectionsUtil.castSet(o, clazz);
        } else {
            return Collections.emptySet();
        }
    }




    /**
     * 执行Get查询，并且携带锁来防止击穿
     *
     * @param rKey      redisKey
     * @param callback  回调方法，根据不同的需求进行不同的DB查询手段，使用方法见参阅
     * @param aliveTime 缓存存活事件
     * @return 从缓存中查询到的数据信息
     */
    public <E> E executeGetWithLock(String rKey,  CacheCallback<E> callback, Long aliveTime) {
        long start = System.currentTimeMillis();
        // 取值
        Object resultFromCache = get(rKey);
        log.info(Strings.toColor("redis尝试第一次取值....", YELLOW));
        // 非空判断，若空就上锁再查询
        if (Objects.isNull(resultFromCache)) {
            log.info(Strings.toColor("redis第一次取值为空！尝试上锁且第二次取值....", RED));
            gLock.lock();
            log.info(Strings.toColor("Locked!", YELLOW));
            try {
                resultFromCache = get(rKey);
                // 若查询到数据仍然为空，就走DB查询
                if (Objects.isNull(resultFromCache)) {
                    log.info(Strings.toColor("redis第二次取值为空！尝试从DB中取值....", RED));
                    E resultFromDB = callback.execute();
                    // 若DB不为空，就刷盘
                    if (Objects.nonNull(resultFromDB)) {
                        log.info(Strings.toColor("DB取值成功！开始往redis刷盘！", GREEN));
                        set(rKey, resultFromDB, aliveTime);
                    } else {
                        // 若为空，则防止穿透刷盘
                        log.info(Strings.toColor("DB取值为空！开始往redis刷入空值！", RED));
                        set(rKey, CommonConstant.EMPTY_STR, 5);
                    }
                    // 直接返回DB查询值
                    log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
                    return resultFromDB;
                }
            } finally {
                gLock.unlock();
                log.info(Strings.toColor("UnLocked", GREEN));
            }
        }
        log.info(Strings.toColor("数据查询完毕！查询总耗时{} ms", GREEN), System.currentTimeMillis() - start);
        return resultFromCache instanceof String ? null : (E) resultFromCache;
    }


}
